package org.dding.msc.service.impl;

import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;

import org.dding.msc.exception.ServiceRegistryException;
import org.dding.msc.service.BatchServiceTarget;
import org.dding.msc.service.Service;
import org.dding.msc.service.ServiceBuilder;
import org.dding.msc.service.ServiceController;
import org.dding.msc.service.ServiceListener;
import org.dding.msc.service.ServiceName;
import org.dding.msc.service.ServiceRegistry;
import org.dding.msc.service.ServiceTarget;
import org.dding.msc.value.ImmediateValue;
import org.dding.msc.value.Value;

/**
 * Abstract base class used for ServiceTargets.
 * 
 */
class ServiceTargetImpl implements ServiceTarget {

	private final ServiceTargetImpl parent;
	private final Map<ServiceListener<Object>, ServiceListener.Inheritance> listeners = Collections
			.synchronizedMap(new IdentityHashMap<ServiceListener<Object>, ServiceListener.Inheritance>());

	ServiceTargetImpl(final ServiceTargetImpl parent) {
		this.parent = parent;
	}

	@Override
	public <T> ServiceBuilder<T> addServiceValue(final ServiceName name,
			final Value<? extends Service<T>> value)
			throws IllegalArgumentException {
		return createServiceBuilder(name, value);
	}

	protected <T> ServiceBuilder<T> createServiceBuilder(
			final ServiceName name, final Value<? extends Service<T>> value)
			throws IllegalArgumentException {
		if (name == null) {
			throw new IllegalArgumentException("name is null");
		}
		if (value == null) {
			throw new IllegalArgumentException("value is null");
		}
		return new ServiceBuilderImpl<T>(this, value, name);
	}

	@Override
	public <T> ServiceBuilder<T> addService(final ServiceName name,
			final Service<T> service) throws IllegalArgumentException {
		return createServiceBuilder(name, new ImmediateValue<Service<T>>(
				service));
	}

	@Override
	public ServiceTarget addListener(ServiceListener<Object> listener) {
		return addListener(ServiceListener.Inheritance.NONE, listener);
	}

	@Override
	public ServiceTarget addListener(ServiceListener<Object>... listeners) {
		return addListener(ServiceListener.Inheritance.NONE, listeners);
	}

	@Override
	public ServiceTarget addListener(
			Collection<ServiceListener<Object>> listeners) {
		return addListener(ServiceListener.Inheritance.NONE, listeners);
	}

	public ServiceTarget addListener(
			final ServiceListener.Inheritance inheritance,
			final ServiceListener<Object> listener) {
		if (listener == null) {
			return this;
		}
		listeners.put(listener, inheritance);
		return this;
	}

	public ServiceTarget addListener(
			final ServiceListener.Inheritance inheritance,
			final ServiceListener<Object>... listeners) {
		if (listeners == null) {
			return this;
		}
		final Map<ServiceListener<Object>, ServiceListener.Inheritance> ourListeners = this.listeners;

		for (ServiceListener<Object> listener : listeners) {
			ourListeners.put(listener, inheritance);
		}
		return this;
	}

	public ServiceTarget addListener(
			final ServiceListener.Inheritance inheritance,
			final Collection<ServiceListener<Object>> listeners) {
		if (listeners == null) {
			return this;
		}
		final Map<ServiceListener<Object>, ServiceListener.Inheritance> ourListeners = this.listeners;

		for (ServiceListener<Object> listener : listeners) {
			ourListeners.put(listener, inheritance);
		}
		return this;
	}

	@Override
	public ServiceTarget removeListener(final ServiceListener<Object> listener) {
		if (listener == null) {
			return this;
		}
		listeners.remove(listener);
		return this;
	}

	@Override
	public Set<ServiceListener<Object>> getListeners() {
		return Collections.unmodifiableSet(listeners.keySet());
	}

	/**
	 * Apply listeners to {@code serviceBuilder}.
	 * 
	 * @param serviceBuilder
	 *            serviceBuilder which listeners will be added to.
	 */
	void apply(ServiceBuilderImpl<?> serviceBuilder) {
		serviceBuilder.addListenerNoCheck(listeners);
	}

	/**
	 * Install {@code serviceBuilder} in this target.
	 * 
	 * @param serviceBuilder
	 *            a serviceBuilder created by this ServiceTarget
	 * 
	 * @return the installed service controller
	 * 
	 * @throws ServiceRegistryException
	 *             if a service registry issue occurred during installation
	 */
	<T> ServiceController<T> install(ServiceBuilderImpl<T> serviceBuilder)
			throws ServiceRegistryException {
		apply(serviceBuilder);
		return parent.install(serviceBuilder);
	}

	/**
	 * Returns the serviceRegistry that contains all services installed by this
	 * target.
	 * 
	 * @return the serviceRegistry containing services installed by this target
	 */
	ServiceRegistry getServiceRegistry() {
		return parent.getServiceRegistry();
	}

	@Override
	public ServiceTarget subTarget() {
		return new ServiceTargetImpl(this);
	}

	@Override
	public BatchServiceTarget batchTarget() {
		return new BatchServiceTargetImpl(this);
	}
}
